//
// Created by 郝羽 on 2018/11/8.
//

#include "catch.hpp"

#include "rtp_history.hpp"

TEST_CASE("unsigned match", "[RtpHistory]") {
    uint16_t a = 65535;
    uint16_t b = 0;
    uint16_t b_minus_a = b - a;
    REQUIRE(b_minus_a == 1);

    int16_t diff = b_minus_a;
    REQUIRE(diff == 1);

    uint16_t a_minus_b = a - b;
    REQUIRE(a_minus_b == 65535);

    int16_t diff2 = a_minus_b;
    REQUIRE(diff2 == -1);
}

TEST_CASE("distance functions", "[RtpHistory]") {
    SECTION("1 is 1 after 0") {
        REQUIRE(RtpHistory<3>::distance(1,0) == 1);
    }

    SECTION("0 is 1 before 1") {
        REQUIRE(RtpHistory<3>::distance(0,1) == -1);
    }

    SECTION("distance should be 0") {
        REQUIRE(RtpHistory<3>::distance(0,0) == 0);
        REQUIRE(RtpHistory<3>::distance(1,1) == 0);
        REQUIRE(RtpHistory<3>::distance(65535,65535) == 0);
        REQUIRE(RtpHistory<3>::distance(32767,32767) == 0);
        REQUIRE(RtpHistory<3>::distance(32768,32768) == 0);
    }

    SECTION("65535 is one before 0") {
        REQUIRE(RtpHistory<3>::distance(65535,0) == -1);
    }

    SECTION("0 is one after 65535") {
        REQUIRE(RtpHistory<3>::distance(0,65535) == 1);
    }

    SECTION("32767 is 32767 after 0") {
        REQUIRE(RtpHistory<3>::distance(32767,0) == 32767);
    }

    SECTION("0 is 32767 before 32767") {
        REQUIRE(RtpHistory<3>::distance(0,32767) == -32767);
    }

    SECTION("can't tell 32768 is before or after 0") {
        REQUIRE(RtpHistory<3>::distance(32768,0) == -32768);
        REQUIRE(RtpHistory<3>::distance(0,32768) == -32768);
    }
}

TEST_CASE("history<3> without seq [  ,  ,  ]", "[RtpHistory]") {
    RtpHistory<3> history;

    SECTION("receive 100") {
        history.add_seq(100);
        auto lost = history.search_consecutive_lost(999);
        REQUIRE(lost.first == 101);
        REQUIRE(lost.second == 0);
    }
}

TEST_CASE("history<3> with two seq [99,  ,97]", "[RtpHistory]") {
    RtpHistory<3> history;

    history.add_seq(97);

    history.add_seq(99);

    SECTION("should found 98 lost") {
        auto lost = history.search_consecutive_lost(999);

        REQUIRE(lost.first == 98);
        REQUIRE(lost.second == 1);
    }

    SECTION("add 98 should found nothing lost") {
        history.add_seq(98);

        auto lost = history.search_consecutive_lost(999);

        REQUIRE(lost.first == 100);
        REQUIRE(lost.second == 0);
    }

    SECTION("add 97 again should have no effect") {
        history.add_seq(97);
        auto lost = history.search_consecutive_lost(999);

        REQUIRE(lost.first == 98);
        REQUIRE(lost.second == 1);
    }

    SECTION("add 96 should have no effect") {
        auto lost = history.search_consecutive_lost(999);

        REQUIRE(lost.first == 98);
        REQUIRE(lost.second == 1);
    }

    SECTION("add 65535 should reset the history") {
        history.add_seq(65535);
        auto lost = history.search_consecutive_lost(999);

        REQUIRE(lost.first == 0);
        REQUIRE(lost.second == 0);
    }

    SECTION("add 0 should reset the history") {
        history.add_seq(0);

        auto lost = history.search_consecutive_lost(999);

        REQUIRE(lost.first == 1);
        REQUIRE(lost.second == 0);
    }
}

TEST_CASE("history<5> with seq [99,  ,  ,  ,  ]", "[RtpHistory]") {
    RtpHistory<5> history;

    history.add_seq(99);

    SECTION("should not reply any missing") {
        auto lost = history.search_consecutive_lost(999);

        REQUIRE(lost.first == 100);
        REQUIRE(lost.second == 0);
    }

    SECTION("add 98, should not report any missing") {
        history.add_seq(98);

        auto lost = history.search_consecutive_lost(999);

        REQUIRE(lost.first == 100);
        REQUIRE(lost.second == 0);
    }

    SECTION("add 104, should trigger the resetting, report nothing missing since 105") {
        history.add_seq(104);

        auto lost = history.search_consecutive_lost(999);

        REQUIRE(lost.first == 105);
        REQUIRE(lost.second == 0);
    }

    SECTION("add 103, will not trigger the resetting, reporting 3 lost since 100") {
        history.add_seq(103);

        auto lost = history.search_consecutive_lost(999);

        REQUIRE(lost.first == 100);
        REQUIRE(lost.second == 3);
    }
}

TEST_CASE("history<100> [100, 199]", "[RtpHistory]") {
    RtpHistory<100> history;
    history.add_seq(199);
    history.add_seq(100);

    SECTION("should report 98 missing since 101") {
        auto lost = history.search_consecutive_lost(999);

        REQUIRE(lost.first == 101);
        REQUIRE(lost.second == 98);
    }

    SECTION("add 299 will trigger the reset") {
        history.add_seq(299);

        auto lost = history.search_consecutive_lost(999);

        REQUIRE(lost.first == 300);
        REQUIRE(lost.second == 0);
    }

    SECTION("add 105, 110, will report missings: {101,4}, {106,4}, {111,88}") {
        history.add_seq(105);
        history.add_seq(110);

        auto lost = history.search_consecutive_lost(999);
        REQUIRE(lost.first == 101);
        REQUIRE(lost.second == 4);

        auto lost2 = history.search_consecutive_lost(101+4, 999);
        REQUIRE(lost2.first == 106);
        REQUIRE(lost2.second == 4);

        auto lost3 = history.search_consecutive_lost(106+4, 999);
        REQUIRE(lost3.first == 111);
        REQUIRE(lost3.second == 88);

        auto lost4 = history.search_consecutive_lost(111+88, 999);
        REQUIRE(lost4.first == 200);
        REQUIRE(lost4.second == 0);
    }

    SECTION("add 110, 105, will report missings: {101,4}, {106,4}, {111,88}") {
        history.add_seq(110);
        history.add_seq(105);


        auto lost = history.search_consecutive_lost(999);
        REQUIRE(lost.first == 101);
        REQUIRE(lost.second == 4);

        auto lost2 = history.search_consecutive_lost(101+4, 999);
        REQUIRE(lost2.first == 106);
        REQUIRE(lost2.second == 4);

        auto lost3 = history.search_consecutive_lost(106+4, 999);
        REQUIRE(lost3.first == 111);
        REQUIRE(lost3.second == 88);

        auto lost4 = history.search_consecutive_lost(111+88, 999);
        REQUIRE(lost4.first == 200);
        REQUIRE(lost4.second == 0);
    }
}

TEST_CASE("history<10> [0, ..., 9]", "[RtpHistory]") {
    RtpHistory<10> history;
    history.add_seq(0);
    history.add_seq(9);

    SECTION("limit lost count to 5") {
        auto lost = history.search_consecutive_lost(5);
        REQUIRE(lost.first == 1);
        REQUIRE(lost.second == 5);
        auto lost2 = history.search_consecutive_lost(1+5, 5);
        REQUIRE(lost2.first == 6);
        REQUIRE(lost2.second == 3);
    }
}

#include <set>

static
const RTPSeq g_RtpSeqGoldens[] = {
    RTPSeq(0u)
    , RTPSeq(1u)
    , RTPSeq(255u)
    , RTPSeq(256u)
    , RTPSeq(1023u)
    , RTPSeq(1024u)
    , RTPSeq(32767u)
    , RTPSeq(32768u)
    , RTPSeq(65535u)
};

TEST_CASE("RTPSeq test distance()", "[RTPSeq]") {
    REQUIRE(RTPSeq(0u).distance(1u) == 1);
    REQUIRE(RTPSeq(0u).distance(2u) == 2);
    REQUIRE(RTPSeq(1u).distance(0u) == -1);
    REQUIRE(RTPSeq(2u).distance(0u) == -2);
    REQUIRE(RTPSeq(65535u).distance(0u) == 1);
    REQUIRE(RTPSeq(65535u).distance(1u) == 2);
    REQUIRE(RTPSeq(0u).distance(65535u) == -1);
    REQUIRE(RTPSeq(1u).distance(65535u) == -2);
    REQUIRE(RTPSeq(0u).distance(32767u) == 32767);
    REQUIRE(RTPSeq(32767u).distance(0u) == -32767);
    REQUIRE(RTPSeq(0u).distance(32768u) == -32768);
    REQUIRE(RTPSeq(32768u).distance(0u) == -32768);
    
    SECTION("self distance 0") {
        for(auto& o : g_RtpSeqGoldens){
            RTPSeq other = o;
            REQUIRE(o.distance(other) == 0);
        }
    }
}

TEST_CASE("RTPSeq test operators", "[RTPSeq]") {
    
    REQUIRE((RTPSeq(0u) + 1).value() == 1);
    REQUIRE((RTPSeq(1u) - 1).value() == 0);
    REQUIRE((RTPSeq(1u) + (-1)).value() == 0);
    REQUIRE((RTPSeq(65535u) + 1).value() == 0);
    REQUIRE((RTPSeq(65535u) + 2).value() == 1);
    REQUIRE((RTPSeq(0u) - 1).value() == 65535);
    REQUIRE((RTPSeq(0u) + (-1)).value() == 65535);
    REQUIRE((RTPSeq(0u) + 32767).value() == 32767);
    REQUIRE((RTPSeq(32767u) - 32767).value() == 0);
    REQUIRE((RTPSeq(0u) + 32768).value() == 32768);
    REQUIRE((RTPSeq(32768u) - 32768).value() == 0);
    
    REQUIRE(RTPSeq(0u) == RTPSeq(0u));
    REQUIRE(RTPSeq(0u) != RTPSeq(1u));
    REQUIRE((RTPSeq(0u)+1) == RTPSeq(1u));
    REQUIRE((RTPSeq(0u)+2) == RTPSeq(2u));
    REQUIRE((RTPSeq(0u)-1) == RTPSeq(65535u));
    REQUIRE((RTPSeq(0u)+(-1)) == RTPSeq(65535u));
    REQUIRE((RTPSeq(0u)-2) == RTPSeq(65534u));
    REQUIRE((RTPSeq(0u)+(-2)) == RTPSeq(65534u));
    REQUIRE((RTPSeq(65535u)+1) == RTPSeq(0u));
    REQUIRE((RTPSeq(65535u)+2) == RTPSeq(1u));
    REQUIRE((RTPSeq(65535u)-1) == RTPSeq(65534u));
    REQUIRE((RTPSeq(65535u)+(-1)) == RTPSeq(65534u));
    REQUIRE((RTPSeq(65535u)-2) == RTPSeq(65533u));
    REQUIRE((RTPSeq(65535u)+(-2)) == RTPSeq(65533u));
    
    SECTION("check self+0 == self") {
        for(auto& o : g_RtpSeqGoldens){
            REQUIRE((o+0).value() == o.value());
        }
    }
    
    SECTION("check seq+d changed") {
        for(auto& o : g_RtpSeqGoldens){
            REQUIRE((o+0) == o);
            REQUIRE((o+1) != o);
            REQUIRE((o-1) != o);
            REQUIRE((o+(-1)) != o);
        }
    }
    
}

TEST_CASE("RTPSeq test withinDistance()", "[RTPSeq]") {
    REQUIRE(RTPSeq(0u).withinDistance(RTPSeq(0u), +0));
    REQUIRE(RTPSeq(0u).withinDistance(RTPSeq(0u), +1));
    REQUIRE(RTPSeq(1u).withinDistance(RTPSeq(0u), +1));
    REQUIRE(RTPSeq(2u).withinDistance(RTPSeq(2u), +2));
    REQUIRE(RTPSeq(65535u).withinDistance(RTPSeq(0u), -1));
    REQUIRE(RTPSeq(65534u).withinDistance(RTPSeq(0u), -2));
    REQUIRE(RTPSeq(0u).withinDistance(RTPSeq(65535u), +1));
    REQUIRE(RTPSeq(1u).withinDistance(RTPSeq(65535u), +2));
    REQUIRE(RTPSeq(65534u).withinDistance(RTPSeq(65535u), -1));
    REQUIRE(RTPSeq(65533u).withinDistance(RTPSeq(65535u), -2));
    
    SECTION("standard [-100 <= seq <= 100]") {
        RTPSeqDistance dist_low = -100;
        RTPSeqDistance dist_high = +100;
        RTPSeqDistance dist_range = (dist_high - dist_low);
        
        for(auto& o : g_RtpSeqGoldens){
            RTPSeq base = o + dist_low;
            for(RTPSeqDistance dist = dist_low; dist <= dist_high; ++dist){
                RTPSeq seq = o + dist;
                REQUIRE(seq.withinDistance(base, dist_range));
            }
        }
        
        for(auto& o : g_RtpSeqGoldens){
            RTPSeq base = o + dist_low;
            for(RTPSeqDistance dist = RTPSEQ_DISTANCE_MIN; dist < dist_low; ++dist){
                RTPSeq seq = o + dist;
                REQUIRE(!seq.withinDistance(base, dist_range));
            }
        }
        
        for(auto& o : g_RtpSeqGoldens){
            RTPSeq base = o + dist_low;
            for(RTPSeqDistance dist = RTPSEQ_DISTANCE_MAX; dist > dist_high; --dist){
                RTPSeq seq = o + dist;
                REQUIRE(!seq.withinDistance(base, dist_range));
            }
        }
    }
    
    SECTION("within [self, self+0)") {
        for(auto& o : g_RtpSeqGoldens){
            RTPSeq other = o;
            REQUIRE(o.withinDistance(other, 0));
        }
    }
    
    SECTION("within [self, self+1]") {
        for(auto& o : g_RtpSeqGoldens){
            RTPSeq other = o;
            REQUIRE(o.withinDistance(other, 1));
        }
    }
    
    SECTION("within [self-1, self)") {
        for(auto& o : g_RtpSeqGoldens){
            RTPSeq other = o;
            REQUIRE(o.withinDistance(other, -1));
        }
    }
    
    SECTION("outside [self, self)") {
        for(auto& o : g_RtpSeqGoldens){
            RTPSeq other = o;
            REQUIRE(!(o-1).withinDistance(other, 0));
            REQUIRE(!(o-1).withinDistance(other, 0));
        }
    }
}

TEST_CASE("RTPSeq test withinRange()", "[RTPSeq]") {
    REQUIRE(RTPSeq(0u).withinRange(RTPSeq(0u), RTPSeq(0u)));
    REQUIRE(RTPSeq(0u).withinRange(RTPSeq(0u), RTPSeq(1u)));
    REQUIRE(RTPSeq(0u).withinRange(RTPSeq(0u), RTPSeq(2u)));
    REQUIRE(RTPSeq(0u).withinRange(RTPSeq(65535u), RTPSeq(0u)));
    REQUIRE(RTPSeq(0u).withinRange(RTPSeq(65534u), RTPSeq(0u)));
    REQUIRE(RTPSeq(0u).withinRange(RTPSeq(65535u), RTPSeq(1u)));
    REQUIRE(RTPSeq(0u).withinRange(RTPSeq(65535u), RTPSeq(2u)));
    REQUIRE(RTPSeq(0u).withinRange(RTPSeq(65534u), RTPSeq(1u)));
    REQUIRE(RTPSeq(0u).withinRange(RTPSeq(65534u), RTPSeq(2u)));
    
    REQUIRE(RTPSeq(65535u).withinRange(RTPSeq(65535u), RTPSeq(65535u)));
    REQUIRE(RTPSeq(65535u).withinRange(RTPSeq(65535u), RTPSeq(0u)));
    REQUIRE(RTPSeq(65535u).withinRange(RTPSeq(65535u), RTPSeq(1u)));
    REQUIRE(RTPSeq(65535u).withinRange(RTPSeq(65534u), RTPSeq(0u)));
    REQUIRE(RTPSeq(65535u).withinRange(RTPSeq(65534u), RTPSeq(1u)));
    REQUIRE(RTPSeq(65535u).withinRange(RTPSeq(65533u), RTPSeq(0u)));
    REQUIRE(RTPSeq(65535u).withinRange(RTPSeq(65533u), RTPSeq(1u)));
    
    REQUIRE(!RTPSeq(0u).withinRange(RTPSeq(65533u), RTPSeq(65535u)));
    REQUIRE(!RTPSeq(65535u).withinRange(RTPSeq(65533u), RTPSeq(65534u)));

    SECTION("withinRange [self, self+0)") {
        for(auto& o : g_RtpSeqGoldens){
            REQUIRE(o.withinRange(o, o+0));
        }
    }
    
    SECTION("withinRange [self, self+1]") {
        for(auto& o : g_RtpSeqGoldens){
            REQUIRE(o.withinRange(o, o+1));
        }
    }
    
    SECTION("withinRange [self-1, self)") {
        for(auto& o : g_RtpSeqGoldens){
            REQUIRE(o.withinRange(o-1, o));
        }
    }
    
    SECTION("outside [self+1, self+1)") {
        for(auto& o : g_RtpSeqGoldens){
            REQUIRE(!o.withinRange(o+1, o+1));
        }
    }
    
    SECTION("outside [self+1, self-1)") {
        for(auto& o : g_RtpSeqGoldens){
            REQUIRE(!o.withinRange(o+1, o-1));
        }
    }
}

TEST_CASE("RTPSeq test isReset()", "[RTPSeq]") {
    for(auto& o : g_RtpSeqGoldens){
        for(RTPSeqDistance dist = RTPSEQ_REST_DISTANCE_LOW
            ; dist <= RTPSEQ_REST_DISTANCE_HIGH
            ; ++dist){
            REQUIRE(!o.isReset(o+dist));
        }
        
        for(RTPSeqDistance dist = RTPSEQ_DISTANCE_MIN
            ; dist < RTPSEQ_REST_DISTANCE_LOW
            ; ++dist){
            RTPSeq next(o+dist);
            REQUIRE(o.isReset(next));
        }

        for(RTPSeqDistance dist = RTPSEQ_DISTANCE_MAX
            ; dist > RTPSEQ_REST_DISTANCE_HIGH
            ; --dist){
            RTPSeq next(o+dist);
            REQUIRE(o.isReset(next));
        }
    }
}

TEST_CASE("NAckUnit test basic", "[NAckUnit]") {
    SECTION("check hole") {
        for(auto& seq : g_RtpSeqGoldens){
            NAckUnit unit(seq);
            uint16_t pid = 0;
            uint16_t blp = 0;
            
            unit.withinAndSet(seq + (NAckUnit::MAX_LENGTH-1));
            unit.buildReq(pid, blp);
            REQUIRE(pid==(seq).value());
            REQUIRE(blp==(uint16_t)0x7FFFu);

            unit.withinAndSet(seq + (NAckUnit::MAX_LENGTH-3));
            unit.buildReq(pid, blp);
            REQUIRE(pid==(seq).value());
            REQUIRE(blp==(uint16_t)0x5FFFu);
            
            unit.resetTo(seq);
            unit.buildReq(pid, blp);
            REQUIRE(pid==(seq).value());
            REQUIRE(blp==(uint16_t)0xFFFFu);
            
            for(RTPSeqDistance len = 1; len < NAckUnit::MAX_LENGTH; ++len){
                unit.buildReq(pid, blp, len);
                REQUIRE(pid==(seq).value());
                REQUIRE(blp==(uint16_t)(0xFFFFu>>(NAckUnit::MAX_LENGTH - len)));
            }
            REQUIRE(!unit.buildReq(pid, blp, 0));
            
        }
    }

    SECTION("check all range") {
        for(auto& seq : g_RtpSeqGoldens){
            NAckUnit unit(seq);
            for(RTPSeqDistance dist = RTPSEQ_DISTANCE_MIN; dist < (0); ++dist){
                REQUIRE(!unit.withinAndSet(seq+dist));
            }
            for(RTPSeqDistance dist = RTPSEQ_DISTANCE_MAX ; dist > (NAckUnit::MAX_LENGTH); --dist){
                REQUIRE(!unit.withinAndSet(seq+dist));
            }
            
            uint16_t pid = 0;
            uint16_t blp = 0;
            unit.buildReq(pid, blp);
            REQUIRE(pid==seq.value());
            REQUIRE(blp==(uint16_t)0xFFFFu);
            for(RTPSeqDistance dist = 0; dist < (NAckUnit::MAX_LENGTH-1); ++dist){
                unit.withinAndSet(seq+dist);
                unit.buildReq(pid, blp);
                REQUIRE(pid==(seq+dist+1).value());
                REQUIRE(blp==(uint16_t)(0xFFFFu>>(dist+1)));
            }
        }
    }
}

class NAckListTester{
    std::vector<uint16_t> input_seqs;
    std::vector<bool> expect_results;
    std::vector<RTPSeq> expect_quick_seqs;
    std::vector<RTPSeq> expect_extract_seqs;
    std::list<uint16_t> out_seqs;
public:
    NAckListTester(){
    }
    
    RTPSeq getLowest(const std::set<RTPSeq, RTPSeqCompartor>& seqs1,
                     const std::set<RTPSeq, RTPSeqCompartor>& seqs2){
        RTPSeq low_seq = *seqs1.begin();
        if(seqs2.size() > 0){
            RTPSeq low_seq2 = *seqs2.begin();
            if(low_seq2.distance(low_seq) > 0){
                low_seq = low_seq2;
            }
        }
        return low_seq;
    }
    
    void build(const std::vector<uint16_t>& seqs, RTPSeqDistance max_range){
        input_seqs = seqs;
        expect_results.clear();
        expect_quick_seqs.clear();
        expect_extract_seqs.clear();
        
        std::set<RTPSeq, RTPSeqCompartor> input_order_seqs; // 当前输入序号，有序
        std::set<RTPSeq, RTPSeqCompartor> extract_order_seqs; // 曾经的空洞，有序
        
        input_order_seqs.insert(input_seqs[0]);
        for(auto& o : input_seqs){
            RTPSeq seq(o);
            RTPSeq high_seq = *input_order_seqs.rbegin();
            RTPSeq low_seq = getLowest(input_order_seqs, extract_order_seqs);
            
            RTPSeqDistance dist = high_seq.distance(seq);
            bool result = false;
            bool drop = false;
            if(dist >= 0){
                if(dist >= max_range){
                    input_order_seqs.clear();
                    extract_order_seqs.clear();
                    low_seq = seq;
                    high_seq = seq;
                    dist = 0;
                }else if(dist > 1){
                    result = true;
                    for(RTPSeqDistance d = 1; d < dist; ++d ){
                        expect_quick_seqs.emplace_back(high_seq+d);
                        extract_order_seqs.insert(high_seq+d);
                    }
                }
            }else{
                dist = low_seq.distance(seq);
                if(dist < -1){
                    if(seq.distance(high_seq) < NAckUnit::MAX_LENGTH){
                        result = true;
                        for(RTPSeqDistance d = dist+1; d < 0; ++d ){
                            expect_quick_seqs.emplace_back(low_seq+d);
                            extract_order_seqs.insert(low_seq+d);
                        }
                    }else{
                        drop = true;
                    }
                }
            }
            expect_results.push_back(result);
            
            if(!drop){
                input_order_seqs.insert(seq);
            }
            
            high_seq = *input_order_seqs.rbegin();
            RTPSeq lowest = getLowest(input_order_seqs, extract_order_seqs);
            while(lowest.distance(high_seq+1) >= max_range){
                for(RTPSeqDistance d = 0; d < NAckUnit::MAX_LENGTH; ++d){
                    input_order_seqs.erase(lowest + d);
                    extract_order_seqs.erase(lowest + d);
                }
                lowest = getLowest(input_order_seqs, extract_order_seqs);
            }
        }
        
        for(auto& o : input_seqs){
            RTPSeq seq(o);
            extract_order_seqs.erase(seq);
        }
        for(auto& o : extract_order_seqs){
            expect_extract_seqs.emplace_back(o);
        }
    }
    
    void dump(){
        std::cout << "NAckListTestCase---" << std::endl;
        std::cout << "  input_seqs (" << input_seqs.size() << "): ";
        for(auto& o : input_seqs){
            std::cout << o << " ";
        }
        std::cout << std::endl;
        
        std::cout << "  expect_results ("<< expect_results.size() << "): ";
        for(auto o : expect_results){
            std::cout << o << " ";
        }
        std::cout << std::endl;
        
        std::cout << "  expect_quick_seqs ("<< expect_quick_seqs.size() << "): ";
        for(auto o : expect_quick_seqs){
            std::cout << o.value() << " ";
        }
        std::cout << std::endl;
        
        std::cout << "  expect_extract_seqs ("<< expect_extract_seqs.size() << "): ";
        for(auto o : expect_extract_seqs){
            std::cout << o.value() << " ";
        }
        std::cout << std::endl;
        
        std::cout << "  out_seqs ("<< out_seqs.size() << "): ";
        for(auto o : out_seqs){
            std::cout << o << " ";
        }
        std::cout << std::endl;
    }
    
    bool verify(bool dumpNAckState = false){
        return verifyCase(input_seqs, expect_results, expect_quick_seqs, expect_extract_seqs
                          , dumpNAckState, &out_seqs);
    } // verify()
    
    static bool verifyCase(const std::vector<uint16_t>& input_seqs,
                           const std::vector<bool>& expect_results,
                           const std::vector<RTPSeq>& expect_quick_seqs,
                           const std::vector<RTPSeq>& expect_extract_seqs,
                           bool dumpNAckState = false,
                           std::list<uint16_t> * pout_seqs = nullptr){
        std::list<uint16_t> seqs_stor;
        std::list<uint16_t> * pseqs = pout_seqs?pout_seqs:&seqs_stor;
        std::list<uint16_t>& out_seqs = *pseqs;
        
        NAckList nackList;
        bool hole = false;

        for(size_t n = 0; n < input_seqs.size(); ++n){
            RTPSeq seq(input_seqs[n]);
            bool expect_hole = expect_results[n];
            
            hole = nackList.addSeq(seq, [&](uint16_t pid, uint16_t blp){
                NAckList::reverseToSeqs(pid, blp, out_seqs);
            });
            //            std::cout << "seq=" << seq.value()
            //            << ", result=" << hole
            //            << ", expect=" << expect_hole << std::endl;
            
            if(dumpNAckState){
                nackList.dump();
            }
            if(hole != expect_hole){
                WARN( "fail to check hole at:"
                     << " n=" << n
                     << " seq=" << seq.value());
                return false;
            }
        }
        
        // verify quick seqs
        if(out_seqs.size() != expect_quick_seqs.size()){
            WARN( "fail to check quick-seqs size:"
                 << " out=" << out_seqs.size()
                 << " expect=" << expect_quick_seqs.size());
            return false;
        }
        size_t n = 0;
        for(auto& o : out_seqs){
            if(o != expect_quick_seqs[n].value()){
                WARN( "fail to check quick-seqs at:"
                     << " out=" << o
                     << " expect=" << expect_quick_seqs[n].value());
                return false;
            }
            ++n;
        }
        
        // verify seqs from extract
        out_seqs.clear();
        nackList.extract([&](uint16_t pid, uint16_t blp){
            NAckList::reverseToSeqs(pid, blp, out_seqs);
        });
        if(out_seqs.size() != expect_extract_seqs.size()){
            WARN( "fail to check extract-seqs size:"
                 << " out=" << out_seqs.size()
                 << " expect=" << expect_extract_seqs.size());
            return false;
        }
        n = 0;
        for(auto& o : out_seqs){
            if(o != expect_extract_seqs[n].value()){
                WARN( "fail to check quick-seqs at:"
                     << " out=" << o
                     << " expect=" << expect_extract_seqs[n].value());
                return false;
            }
            ++n;
        }
        return true;
    } // verifyCase()
    
    static std::vector<RTPSeq> toRTPSeq(const std::vector<uint16_t>& input_seqs){
        std::vector<RTPSeq> seqs;
        for(auto& o : input_seqs){
            seqs.push_back(RTPSeq(o));
        }
        return seqs;
    }
    
}; // NAckListTester


TEST_CASE("NAckList basic tests", "[NAckList]") {

    SECTION("test reverseToSeqs") {
        
        std::vector<uint16_t> cases[][2] ={
            {
                { 0, 0xFFFFu}, // pid, blp
                { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, // seqs
            },
            {
                { 100, 0x5A5Au }, // 0x5A5A=0101-1010-0101-1010
                { 100, 102, 104, 105, 107, 110, 112, 113, 115 },
            },
            {
                { 100, 0xA5A5u }, // 0xA5A5u=1010-0101-1010-0101
                { 100, 101, 103, 106, 108, 109, 111, 114, 116 },
            },
            {
                { 65534, 0x5A5Au },
                { 65534, 0, 2, 3, 5, 8, 10, 11, 13 },
            },
            {
                { 65534, 0xA5A5u },
                { 65534, 65535, 1, 4, 6, 7, 9, 12, 14 },
            },
            {
                { 65535, 0x5A5Au },
                { 65535, 1, 3, 4, 6, 9, 11, 12, 14 },
            },
            {
                { 65535, 0xA5A5u },
                { 65535, 0, 2, 5, 7, 8, 10, 13, 15 },
            },
        } ;
        size_t num_cases = sizeof(cases)/sizeof(cases[0]);
        
        for(size_t z = 0; z < num_cases; ++z){
            DYNAMIC_SECTION( "test sub case " << z) {
                std::vector<uint16_t>& pid_blp = cases[z][0];
                std::vector<uint16_t>& std_seqs = cases[z][1];
                REQUIRE(pid_blp.size() == 2);
                
                uint16_t pid = pid_blp[0];
                uint16_t blp = pid_blp[1];
                std::list<uint16_t> seqs;
                NAckList::reverseToSeqs(pid, blp, seqs);
                REQUIRE(seqs.size() == std_seqs.size());
                uint16_t n = 0;
                for(auto& o : seqs){
                    REQUIRE(o == std_seqs[n]);
                    ++n;
                }
            }
        }
    }
    
    SECTION("manual test hole") {
        std::vector<uint16_t> cases[][3] ={
            {
                { 100, 0, 101, 0, 103, 1 , 105, 1}, // seq, result, seq, result ...
                { 102, 104}, // quick nack seqs
                { 102, 104}, // extract nack seqs
            },

            {
                { 100, 0, 101, 0, 90, 1 , 102, 0, 95, 0},
                { 91, 92, 93, 94, 95, 96, 97, 98, 99 },
                { 91, 92, 93, 94,     96, 97, 98, 99 },
            },
            
            {
                { 100, 0, 101, 0, 99, 0, 90, 1, 88, 1, 102, 0, 95, 0},
                { 91, 92, 93, 94, 95, 96, 97, 98, 89 },
                { 89, 91, 92, 93, 94,     96, 97, 98 },
            },
            
            {
                {
                    100, 0, 101, 0, 102, 0, 103, 0, 104, 0, 105, 0, 106, 0, 107, 0,
                    108, 0, 109, 0, 110, 0, 111, 0, 112, 0, 113, 0, 114, 0, 115, 0,
                    116, 0, 117, 0, 118, 0
                },
                { },
                { },
            },
            
            {
                { 100, 0, 118, 1, 103, 0, 120, 1},
                {
                    101, 102, 103, 104, 105, 106, 107, 108,
                    109, 110, 111, 112, 113, 114, 115, 116,
                    117, 119
                },
                {
                    101, 102,      104, 105, 106, 107, 108,
                    109, 110, 111, 112, 113, 114, 115, 116,
                    117, 119
                },
            },
            
            {
                { 65534, 0, 10, 1, 17, 1, 3, 0, 12, 0, 18, 0, 19, 0},
                {
                    65535, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16
                },
                {
                    65535, 0, 1, 2,    4, 5, 6, 7, 8, 9, 11,     13, 14, 15, 16
                },
            },
        } ;
        size_t num_cases = sizeof(cases)/sizeof(cases[0]);
        
        for(size_t z = 0; z < num_cases; ++z){
            DYNAMIC_SECTION( "test sub case " << z) {
                std::vector<uint16_t>& seq_n_holes = cases[z][0];
                std::vector<uint16_t>& quick_seqs = cases[z][1];
                std::vector<uint16_t>& extract_seqs = cases[z][2];
                REQUIRE(seq_n_holes.size()%2 == 0);

                std::vector<uint16_t> input_seqs;
                std::vector<bool> expect_results;
                for(size_t n = 0; n < seq_n_holes.size(); n += 2){
                    input_seqs.push_back(seq_n_holes[n]);
                    expect_results.push_back(seq_n_holes[n+1]);
                }

                REQUIRE(NAckListTester::verifyCase(input_seqs, expect_results
                           , NAckListTester::toRTPSeq(quick_seqs)
                           , NAckListTester::toRTPSeq(extract_seqs)));
            } // DYNAMIC_SECTION
        } // for
    } // SECTION
    
    SECTION("semi-auto test hole 1") {
        std::vector<uint16_t> cases[] ={
            { 100, 101, 99, 90, 88, 102, 95, 1},
            { 65533, 5, 10, 14, 15, 20, 22, 1},
        } ;
        size_t num_cases = sizeof(cases)/sizeof(cases[0]);
        for(size_t z = 0; z < num_cases; ++z){
            DYNAMIC_SECTION( "test sub case " << z) {
                std::vector<uint16_t>& input_seqs = cases[z];
                NAckListTester tester;
                tester.build(input_seqs, NAckList::MAX_DISTANCE);
                bool r = tester.verify();
                tester.dump();
                REQUIRE(r);
                
            } // DYNAMIC_SECTION
        } // for
    }// SECTION
    
    SECTION("semi-auto test hole 2") {
        std::vector<uint16_t> cases[] ={
            { 100, 101, 99, 90, 88, 102, 95 },
            { 1, 254, 99, 90, 88, 102, 95 },
            { 1, 255, 99, 90, 88, 102, 95 },
            { 1, 256, 99, 90, 88, 102, 95 },
        } ;
        size_t num_cases = sizeof(cases)/sizeof(cases[0]);
        for(size_t z = 0; z < num_cases; ++z){
            DYNAMIC_SECTION( "test sub case " << z) {
                std::vector<uint16_t>& input_seqs = cases[z];
                NAckListTester tester;
                tester.build(input_seqs, NAckList::MAX_DISTANCE);
                bool r = tester.verify();
                REQUIRE(r);
            } // DYNAMIC_SECTION
        } // for
    }// SECTION
}

